/* eslint-disable @typescript-eslint/no-explicit-any */
export type HttpMethod =
  | "GET"
  | "POST"
  | "PUT"
  | "PATCH"
  | "DELETE"
  | "HEAD"
  | "OPTIONS";
export type ResponseType = "json" | "text" | "blob";

export class ApiError<T = unknown> extends Error {
  status: number;
  payload?: T;
  url: string;
  headers: Headers;
  constructor(
    message: string,
    status: number,
    url: string,
    headers: Headers = new Headers(),
    payload?: T
  ) {
    super(message);
    this.name = "ApiError";
    this.status = status;
    this.url = url;
    this.payload = payload;
    this.headers = headers;
  }
}
export const isApiError = (e: unknown): e is ApiError => e instanceof ApiError;

const isBrowser = () => typeof window !== "undefined";
const DEFAULT_BASE = (process.env.NEXT_PUBLIC_API_URL || "/api").replace(
  /\/+$/,
  ""
);

function isAbsoluteUrl(url: string) {
  return /^https?:\/\//i.test(url);
}

export function buildUrl(
  path: string,
  query?: Record<string, any>,
  base = DEFAULT_BASE
) {
  const p = path.startsWith("/") ? path : `/${path}`;
  const url = isAbsoluteUrl(path) ? path : `${base}${p}`;
  if (!query || Object.keys(query).length === 0) return url;

  const usp = new URLSearchParams();
  for (const [k, v] of Object.entries(query)) {
    if (v === undefined || v === null) continue;
    if (Array.isArray(v)) v.forEach((vv) => usp.append(k, String(vv)));
    else usp.append(k, String(v));
  }
  const qs = usp.toString();
  return qs ? `${url}?${qs}` : url;
}

function safeJson(text: string) {
  try {
    return JSON.parse(text);
  } catch {
    return text;
  }
}

function sleep(ms: number) {
  return new Promise((r) => setTimeout(r, ms));
}

/** 请求拦截器形态 */
export type RequestInterceptor = (ctx: {
  url: string;
  init: RequestInit;
  options: RequestOptions<any>;
}) =>
  | Promise<{ url: string; init: RequestInit; options: RequestOptions<any> }>
  | { url: string; init: RequestInit; options: RequestOptions<any> };

/** 响应拦截器形态（可用于统一处理特定返回码/包裹格式等） */
export type ResponseInterceptor = (ctx: {
  response: Response;
  request: { url: string; init: RequestInit; options: RequestOptions<any> };
}) => Promise<Response> | Response;

/** 全局可注册的拦截器（在应用初始化处按需 push） */
export const requestInterceptors: RequestInterceptor[] = [];
export const responseInterceptors: ResponseInterceptor[] = [];

export type RequestOptions<TBody> = {
  method?: HttpMethod;
  baseURL?: string;
  headers?: Record<string, string>;
  /** 直接传对象即可，FormData 会自动识别。 */
  body?: TBody;
  /** 追加到 URL 的查询参数 */
  query?: Record<string, any>;
  /** "json" | "text" | "blob"，默认 "json" */
  responseType?: ResponseType;
  /** 超时时间（ms），默认 15000 */
  timeoutMs?: number;
  /** 401 时是否自动跳转到 /login，默认 true（仅浏览器端） */
  redirectOn401?: boolean;
  /** 401 时不跳转的路径前缀（默认包含 /login） */
  noRedirectPrefixesOn401?: string[];
  /** 重试次数，默认 0 */
  retries?: number;
  /** 首次重试延时（ms），默认 300，指数退避 */
  retryDelayMs?: number;
  /** 传自定义 AbortSignal */
  signal?: AbortSignal;
  /** 仅在浏览器 fetch 里用，默认 "include" 以便带 Cookie */
  credentials?: RequestCredentials;
  /** 缓存策略，默认 "no-store" */
  cache?: RequestCache;
  /** 可选：对返回数据做 schema 校验（如 zod.parse） */
  schemaValidate?: (data: any) => any;
  /** 关闭 dev 日志 */
  silent?: boolean;
};

function isFormData(body: unknown): body is FormData {
  return typeof FormData !== "undefined" && body instanceof FormData;
}

/** 读取响应并按类型解析 */
async function readResponse(
  res: Response,
  responseType: ResponseType
): Promise<unknown> {
  if (responseType === "blob") return await res.blob();
  const text = await res.text();
  if (responseType === "text") return text;
  if (!text) return null;
  return safeJson(text);
}

/** 合并多个 AbortSignal（当你既传入自定义 signal 又设置了超时） */
class AbortControllerLink {
  controller: AbortController;
  constructor(signals: (AbortSignal | undefined)[]) {
    this.controller = new AbortController();
    signals.forEach((s) => {
      if (!s) return;
      if (s.aborted) this.controller.abort();
      s.addEventListener("abort", () => this.controller.abort(), {
        once: true,
      });
    });
  }
  get signal() {
    return this.controller.signal;
  }
}

/** 提取通用错误消息 */
function extractErrorMessage(payload: unknown, fallback: string): string {
  if (!payload) return fallback;
  if (typeof payload === "string") return payload;

  const p: any = payload;
  // FastAPI 常见
  if (typeof p.detail === "string") return p.detail;
  if (Array.isArray(p.detail) && p.detail[0]?.msg)
    return String(p.detail[0].msg);
  // 你的自定义
  if (typeof p.msg === "string") return p.msg;
  if (typeof p.message === "string") return p.message;

  return fallback;
}

/** 底层统一请求 */
export async function apiRequest<TResp = unknown, TBody = unknown>(
  path: string,
  opts: RequestOptions<TBody> = {}
): Promise<TResp> {
  const {
    method = "GET",
    baseURL = DEFAULT_BASE,
    headers = {},
    body,
    query,
    responseType = "json",
    timeoutMs = 15000,
    redirectOn401 = true,
    noRedirectPrefixesOn401 = ["/login"],
    retries = 0,
    retryDelayMs = 300,
    signal,
    credentials = "include",
    cache = "no-store",
    schemaValidate,
    silent = false,
  } = opts;

  let url = buildUrl(path, query, baseURL);

  // 基础 RequestInit
  const baseInit: RequestInit = {
    method,
    headers: {
      ...(body && !isFormData(body)
        ? { "Content-Type": "application/json" }
        : {}),
      ...headers,
    },
    credentials,
    cache,
    body: body ? (isFormData(body) ? body : JSON.stringify(body)) : undefined,
  };

  // 请求拦截器（一次）
  let options = opts;
  for (const itc of requestInterceptors) {
    const res = await itc({ url, init: baseInit, options });
    url = res.url;
    options = res.options;
  }
  const init = baseInit;

  const doFetch = async (
    reqUrl: string,
    reqInit: RequestInit
  ): Promise<Response> => {
    let res = await fetch(reqUrl, reqInit);
    for (const itc of responseInterceptors) {
      const processed = await itc({
        response: res,
        request: { url: reqUrl, init: reqInit, options },
      });
      res = processed;
    }
    return res;
  };

  let attempt = 0;
  let lastErr: any = null;

  while (attempt <= retries) {
    const perAttemptController = new AbortController();
    const linked = signal
      ? new AbortControllerLink([signal, perAttemptController.signal]).signal
      : perAttemptController.signal;
    const attemptInit: RequestInit = { ...init, signal: linked };
    const timeoutId = setTimeout(() => perAttemptController.abort(), timeoutMs);

    try {
      const res = await doFetch(url, attemptInit);
      clearTimeout(timeoutId);

      // 401：仅浏览器下自动跳转，且当前页面不在免跳前缀
      if (res.status === 401 && isBrowser() && redirectOn401) {
        const current = window.location.pathname;
        const shouldSkip = noRedirectPrefixesOn401.some(
          (p) => current === p || current.startsWith(p + "/")
        );
        if (!shouldSkip) {
          const next = encodeURIComponent(current + window.location.search);
          window.location.href = `/login?next=${next}`;
          // 阻止后续逻辑
          return new Promise<TResp>(() => {});
        }
      }

      if (!res.ok) {
        const payload = await readResponse(res, responseType);
        throw new ApiError(
          extractErrorMessage(payload, res.statusText || `HTTP ${res.status}`),
          res.status,
          url,
          res.headers,
          payload
        );
      }

      // 读取数据
      const data = await readResponse(res, responseType);

      // —— 新增：识别 {code,msg,data} 包裹并拆包 —— //
      if (data && typeof data === "object" && "code" in (data as any)) {
        const d: any = data;
        const code = d.code;
        if (typeof code === "number" && code !== 0) {
          // 业务错误
          const msg = extractErrorMessage(
            d,
            res.statusText ||
              `Business Error${res.status ? ` (HTTP ${res.status})` : ""}`
          );
          throw new ApiError(
            String(msg),
            res.status || 200,
            url,
            res.headers,
            data
          );
        }
        const unwrapped = "data" in d ? d.data : null;
        const out = schemaValidate ? schemaValidate(unwrapped) : unwrapped;
        if (!silent && process.env.NODE_ENV !== "production") {
          // eslint-disable-next-line no-console
          console.debug(
            `[apiRequest] ${method} ${url} ->`,
            res.status,
            "(unwrap)"
          );
        }
        return out as TResp;
      }

      // 非包裹格式，按原样返回
      const out = schemaValidate ? schemaValidate(data) : data;
      if (!silent && process.env.NODE_ENV !== "production") {
        // eslint-disable-next-line no-console
        console.debug(`[apiRequest] ${method} ${url} ->`, res.status);
      }
      return out as TResp;
    } catch (e: any) {
      clearTimeout(timeoutId);
      lastErr = e;

      if (e?.name === "AbortError") {
        if (signal?.aborted) {
          throw new ApiError("请求已取消", 499, url, new Headers());
        }
        if (attempt === retries) {
          throw new ApiError("请求超时", 408, url, new Headers());
        }
      } else if (isApiError(e)) {
        const s = e.status;
        const retriable = s === 429 || (s >= 500 && s < 600);
        if (!retriable || attempt === retries) throw e;
      } else if (attempt === retries) {
        throw new ApiError(e?.message || "网络错误", 0, url, new Headers());
      }

      await sleep(retryDelayMs * Math.pow(2, attempt));
      attempt += 1;
    }
  }

  throw lastErr || new ApiError("未知错误", 0, url, new Headers());
}

/** 语法糖 */
export const api = {
  get: <T>(
    path: string,
    opts?: Omit<RequestOptions<never>, "method" | "body">
  ) => apiRequest<T, never>(path, { ...opts, method: "GET" }),
  delete: <T>(
    path: string,
    opts?: Omit<RequestOptions<never>, "method" | "body">
  ) => apiRequest<T, never>(path, { ...opts, method: "DELETE" }),
  head: <T>(
    path: string,
    opts?: Omit<RequestOptions<never>, "method" | "body">
  ) => apiRequest<T, never>(path, { ...opts, method: "HEAD" }),
  post: <T, B = unknown>(
    path: string,
    body?: B,
    opts?: Omit<RequestOptions<B>, "method">
  ) => apiRequest<T, B>(path, { ...(opts || {}), method: "POST", body }),
  put: <T, B = unknown>(
    path: string,
    body?: B,
    opts?: Omit<RequestOptions<B>, "method">
  ) => apiRequest<T, B>(path, { ...(opts || {}), method: "PUT", body }),
  patch: <T, B = unknown>(
    path: string,
    body?: B,
    opts?: Omit<RequestOptions<B>, "method">
  ) => apiRequest<T, B>(path, { ...(opts || {}), method: "PATCH", body }),
};

/** 文件上传（FormData）。自动省略 JSON Content-Type。 */
export function upload<T = unknown>(
  path: string,
  formData: FormData,
  opts?: Omit<RequestOptions<FormData>, "body" | "method">
) {
  return apiRequest<T, FormData>(path, {
    ...(opts || {}),
    method: "POST",
    body: formData,
  });
}

/** 文件下载（返回 Blob）。可配合 URL.createObjectURL 触发保存。 */
export function download(
  path: string,
  opts?: Omit<RequestOptions<never>, "responseType">
) {
  return apiRequest<Blob, never>(path, {
    ...(opts || {}),
    method: "GET",
    responseType: "blob",
  });
}

/** SWR/React Query 友好的 fetcher（JSON） */
export const swrJsonFetcher = <T>(path: string) => api.get<T>(path);
